﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Zhy.DigitaPlatform.DeviceAccess.Base;

namespace Zhy.DigitaPlatform.DeviceAccess.Execute
{
    public abstract class ModbusBase : ExecuteObject
    {
        /// <summary>
        /// 异常功能码
        /// </summary>
        protected static Dictionary<int, string> Errors = new Dictionary<int, string>
        {
            { 0x01, "非法功能码"},
            { 0x02, "非法数据地址"},
            { 0x03, "非法数据值"},
            { 0x04, "从站设备故障"},
            { 0x05, "确认，从站需要一个耗时操作"},
            { 0x06, "从站忙"},
            { 0x08, "存储奇偶性差错"},
            { 0x0A, "不可用网关路径"},
            { 0x0B, "网关目标设备响应失败"},
        };

        /// <summary>
        /// 将要获取的Modbus-根据功能码进行分组
        /// 将相同的功能码整理成一次访问，使用这种方式减少与硬件的设备的通讯次数。
        /// </summary>
        /// <param name="variables"></param>
        /// <returns></returns>
        public override Result<List<CommAddress>> GroupAddress(List<VariableProperty> variables)
        {
            Result<List<CommAddress>> result = new Result<List<CommAddress>>();
            result.Data = new List<CommAddress>();
            try
            {
                // N个打包地址
                List<ModbusAddress> address = new List<ModbusAddress>();
                // 针对Modbus地址   :string "40001" "40011"
                foreach (var item in variables) // 根据地址名称-对请求的地址进行分组
                {
                    // 将"40001"地址解析成   03  01  02
                    // 目的将所有同类型功能码的地址整合在一起，不考虑长度
                    var maCurrent = this.AnalysisAddress(item);
                    maCurrent.VariableId = item.VarId;
                    // 
                    // 获取modbus通讯对象-用于分组判断
                    var addr = address.FirstOrDefault(a => a.FuncCode == maCurrent.FuncCode);
                    if (addr == null)
                    {
                        var ma = new ModbusAddress(maCurrent); // 获取当前的通讯变量
                        ma.Variables.Add(maCurrent); // 添加具体的通讯地址变量
                        address.Add(ma);


                    }
                    else
                    { // 功能码添加过需要计算一下获取数据的长度
                        // 调整
                        /// 40010 - 1、40020 - 2、40024 - 2、40030 - 1、40015 - 2、40005 - 1、40001 - 1、40100 - 2、40200 - 1
                        /// start:9   len:1                        condition
                        /// start:9   len:12    : 19 - 9 + 2       19 > 9  &&  19+2>9+1
                        /// start:9   len:16    : 23 - 9 + 2       23 > 9  &&  23+2>9+12
                        /// start:9   len:21    : 29 - 9 + 1       29 > 9  &&  29+1>9+16
                        /// start:9   len:21    : None             14 > 9  &&  14+2>9+21   X
                        /// start:4   len:26    : 9 - 4 + 21       4  < 9
                        /// start:0   len:30    : 4 - 0 + 26       0  < 4
                        /// 
                        /// 1、找出同类功能码的
                        /// 2、进行起始地址排序
                        /// 3、最大地址-最小地址+最大地址的长度
                        /// 
                        /// 前提是将所有地址都转换出来
                        /// 这个处理方式，自行尝试！！！
                       /// 分组计算请求地址的起始位置以及请求地址的长度
                        if (maCurrent.StartAddress > addr.StartAddress)
                        {
                            if (maCurrent.StartAddress + maCurrent.Length > addr.StartAddress + addr.Length)
                                addr.Length = maCurrent.StartAddress - addr.StartAddress + maCurrent.Length;
                        }
                        else if (maCurrent.StartAddress < addr.StartAddress)
                        {
                            addr.Length += addr.StartAddress - maCurrent.StartAddress;
                            addr.StartAddress = maCurrent.StartAddress;
                        }

                        addr.Variables.Add(maCurrent); // 添加通讯变量
                    }
                }
                address.ForEach(a => result.Data.Add(a));
            }
            catch (Exception ex)
            {
                result.Status = false;
                result.Message = ex.Message;
            }
            return result;
        }

        /// <summary>
        /// 根据数据类型计算所需的寄存器数量
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private ModbusAddress AnalysisAddress(VariableProperty item)
        {
            ModbusAddress ma = new ModbusAddress();
            // 根据数据类型计算所需的寄存器数量
            int typeLen = Marshal.SizeOf(item.ValueType);
            ma.Length = typeLen / 2;    // 常规   特殊使用：字符串

            if (item.VarAddr.StartsWith("0"))
            {
                ma.FuncCode = 01;
                ma.Length = 1;
            }
            else if (item.VarAddr.StartsWith("1"))
            {
                ma.FuncCode = 02;
                ma.Length = 1;
            }
            else if (item.VarAddr.StartsWith("3"))
                ma.FuncCode = 04;
            else if (item.VarAddr.StartsWith("4"))
                ma.FuncCode = 03;

            // 起始地址
            ma.StartAddress = int.Parse(item.VarAddr.Substring(1)) - 1;// 关于减1的动作，可以通过配置来确定

            return ma;
        }

        /// <summary>
        /// 组建请求报文
        /// </summary>
        /// <param name="slaveNum">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="startAddr">起始地址</param>
        /// <param name="count">请求数量</param>
        /// <returns></returns>
        protected List<byte> CreateReadPDU(byte slaveNum, byte funcCode, ushort startAddr, ushort count)
        {
            List<byte> datas = new List<byte>();
            datas.Add(slaveNum);
            datas.Add(funcCode);

            datas.Add((byte)(startAddr / 256));
            datas.Add((byte)(startAddr % 256));

            datas.Add((byte)(count / 256));
            datas.Add((byte)(count % 256));

            return datas;
        }

        /// <summary>
        /// 读取组件通讯数据
        /// </summary>
        /// <param name="mas">一次性读取多个属性值</param>
        /// <returns></returns>
        public override Result Read(List<CommAddress> variables)
        {
            /// 1、变量离散   不连续
            /// 2、可以每个每个的变量进行数据读取，效率低，  需要做打包
            ///     40001、40010、40080、40100、40200、00001、00002
            /// 3、通信库封装的时候，做了一个动作，长度自动分片
            /// 4、进行同类存储区的地址整合
            ///    40001 -》  请求200个
            ///    03   01  200
            /// 
            /// 
            /// 5、200个地址    把5个地址对应的数据挑出来，写入到参数中去
            Result result = new Result();
            try
            {
                // 获取从站地址
                var prop = this.Props.FirstOrDefault(p => p.PropName == "SlaveId");
                if (prop == null)
                    throw new Exception("未配置从站地址");

                byte slaveId = 0x01; // 设置默认的从站地址
                byte.TryParse(prop.PropValue, out slaveId);

                foreach (ModbusAddress ma in variables)
                {
                    #region 如果读取的地址长度超过120，就分次读取最后在整合到一起
                    // 问题：打包的地址并没有做长度检查
                    ushort max = 120;// 默认情况  每次请求最多支持120个寄存器
                    int reqTotalCount = ma.Length;// 一共需要读多少个寄存器，ma.Length-》寄存器数据   一个寄存器两个字节
                    if (ma.FuncCode == 0x01 || ma.FuncCode == 0x02)
                    {
                        max = 240 * 8;
                    }

                    List<byte> bytes = new List<byte>();
                    ushort startAddr = (ushort)ma.StartAddress;

                    for (ushort i = 0; i < reqTotalCount; i += max)
                    {
                        startAddr += i;  // i是每次要读取的寄存器数量
                        var perCount = (ushort)Math.Min(reqTotalCount - i, max); // 获取最小值
                        var dataBytesLen = perCount * 2; // 计算要读取的字节数
                        if (ma.FuncCode == 0x01 || ma.FuncCode == 0x02)  // 判断功能码
                            dataBytesLen = (int)Math.Ceiling(perCount * 1.0 / 8);  // 读取线圈的状态码判断

                        // 读取modbus通讯数据
                        bytes.AddRange(this.Read(slaveId, (byte)ma.FuncCode, startAddr, perCount, (ushort)dataBytesLen));
                    }
                    #endregion

                    // 寄存器
                    if (new int[] { 03, 04 }.Contains(ma.FuncCode))
                    {
                        // 需要知道变量列表   地址长度
                        //item.Addresses[0].StartAddress
                        //item.Addresses[0].Length
                        for (int i = 0; i < ma.Variables.Count; i++) // 循环所有地址
                        {
                            var addr = ma.Variables[i] as ModbusAddress; // 当前地址
                            var start = addr.StartAddress - ma.StartAddress; // 获取起始索引：当前起始地址-分组的起始地址 
                            var len = addr.Length * 2;  // 获取的是字节数量
                            byte[] dataBytes = bytes.GetRange(start * 2, len).ToArray(); // 获取当前地址数据
                                                                                         // 赋值当前地址数据 
                            addr.ValueBytes = this.SwitchEndianType(new List<byte>(dataBytes)).ToArray();
                        }
                    }
                    // 线圈  Byte->8个状态 
                    else if (new int[] { 01, 02 }.Contains(ma.FuncCode))
                    {
                        // 状态全部转出来   
                        // 根据位置进行结果获取   0x00  0x01
                        List<byte> resultState = new List<byte>(); // 获取所有的位
                        bytes.ForEach(b =>
                        {
                            for (int i = 0; i < 8; i++) // 一个位8个字节
                            {
                                resultState.Add((byte)((b & (1 << i)) >> i));
                            }
                        });
                        for (int i = 0; i < ma.Variables.Count; i++)
                        {
                            var addr = ma.Variables[i] as ModbusAddress; // 当前地址
                            var start = addr.StartAddress - ma.StartAddress; // 获取起始索引：当前起始地址-分组的起始地址 
                            addr.ValueBytes = resultState.GetRange(start, 1).ToArray(); // 获取当前地址数据
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                result.Status = false;
                result.Message = ex.Message;
            }
            return result;
        }

        /// <summary>
        /// 读取Modbus通讯数据
        /// </summary>
        /// <param name="slaveNum">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="startAddr">起始地址</param>
        /// <param name="count">读取数量</param>
        /// <param name="respLen"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected virtual List<byte> Read(byte slaveNum, byte funcCode, ushort startAddr, ushort count, ushort respLen)
        {
            return null;
        }
    }
}
